import java.util.Random;

import akka.actor.ActorRef;
import akka.actor.UntypedActor;

/**
 * Arbiter class which supplies the smokers with cigarette ingredients.
 * These ingredients are Tobacco, Paper and a Match.
 * 
 * @author Peter-John Rowe
 *
 */
public class Arbiter extends UntypedActor {

	/**Table on which ingredients are placed on*/
	private final ActorRef table;
	/**Smokers that utilise the ingredients to make cigarettes*/
	private final ActorRef[] smokers;

	/**The total number of ingredients available to the smokers*/
	private final int NUMBER_OF_AVAILABLE_INGREDIENTS = 3;
	/**The total number of ingredients placed at each round*/
	private final int NUMBER_OF_PLACED_INGREDIENTS = 2;

	/**Tobacco index - For random ingredient selection*/
	private final int TOBACCO_INDEX = 1;
	/**Paper index - For random ingredient selection*/
	private final int PAPER_INDEX = 2;
	/**Match index - For random ingredient selection*/
	private final int MATCH_INDEX = 3;

	/**Randomly selects an index associated with an Ingredient*/
	private Random indexGenerator;

	/**Specifies the type of ingredient used upon intialisation*/
	private final String TOBACCO_STRING = "Tobacco";
	private final String PAPER_STRING = "Paper";
	private final String MATCH_STRING = "Match";
	
	/**
	 * Arbiter constructor. 
	 * 
	 * @param table table which ingredients are placed on
	 * @param smokers smokers that use the ingredients supplied.
	 */
	public Arbiter(ActorRef table,ActorRef[] smokers) {
		this.table = table;
		this.smokers = smokers;
		indexGenerator = new Random();
	}

	/**
	 * Randomly select two ingredients.
	 * 
	 * @return selectedIngredients resulting ingredients
	 */
	private Ingredient[] selectIngredients() {

		// Initialize ingredients' holder
		Ingredient[] selectedIngredients = new Ingredient[2];

		// Ensures that 2 Ingredients are selected
		for(int count = 0; count < NUMBER_OF_PLACED_INGREDIENTS; count++) {

			// Randomly selects an Ingredient to be placed
			switch(indexGenerator.nextInt(NUMBER_OF_AVAILABLE_INGREDIENTS)+1) {
			case TOBACCO_INDEX:
				selectedIngredients[count] = new Ingredient(TOBACCO_STRING);
				break;
			case PAPER_INDEX: 
				selectedIngredients[count] = new Ingredient(PAPER_STRING);
				break;
			case MATCH_INDEX: 				
				selectedIngredients[count] = new Ingredient(MATCH_STRING);
				break;
			}

		}

		return selectedIngredients;
	}
	
	/**
	 * Place the selected ingredients on the table.
	 * 
	 * @param ingredients the selected ingredients.
	 */
	private void placeIngredientsOnTable(Ingredient[] ingredients) {
		table.tell(new PlaceIngredientsMessage(ingredients));
	}

	/**
	 * Notify all the smokers that the ingredients were placed
	 */
	private void notifySmokers() {
		
		for(ActorRef smoker:smokers) {
			smoker.tell(new NotifyMessage());
		}
	}
	
	/**
	 * Actions to be performed when this actor receives a message.
	 * 
	 * @param message 
	 */
	public void onReceive(Object message) throws Exception {

		// When start message is received
		if (message instanceof StartMessage) {
			// Select ingredients and print action
			Ingredient[] selectedIngredients = selectIngredients();
			// State ingredient selection
			System.out.format("Arbiter: Selected both %s and %s to be placed on the table %s",
					selectedIngredients[0].getIngredientName(),selectedIngredients[1].getIngredientName(),Main.NEW_LINE);
			
			// Place ingredients on table
			placeIngredientsOnTable(selectedIngredients);
			// State ingredient placement
			System.out.format("Arbiter: Placed both %s and %s on the table %s",
					selectedIngredients[0].getIngredientName(),selectedIngredients[1].getIngredientName(),Main.NEW_LINE);
			
			// Notifies smokers that ingredients were placed
			notifySmokers();
			// State smoker notification
			System.out.format("Arbiter: All smokers were notified %s",Main.NEW_LINE);
		}

		// When Stop message is received
		if (message instanceof StopMessage) { 
			// Propagates the stop message through to the table
			table.tell(new StopMessage());

			// Propagates the stop message through to the smokers
			for(ActorRef smoker: smokers) {
				smoker.tell(new StopMessage());
			}

			// Closes actor
			getContext().stop();
		}
	}

} // Arbiter Class
